package Fifteen;

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JButton;
import javax.swing.Timer;

////////////////////////////////////////////////////////////////////////////////
//CLASS MOVABLEBUTTON
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//Begin definition class MovableButton
//Class MovableButton extends JButton and implements MouseListener, 
//MouseMotionListener, and ActionListener so that the button can be dragged
//and sliding of buttons can be animated in a fashion consistent with the 
//GUI and the puzzle logic.
////////////////////////////////////////////////////////////////////////////////
class MovableButton extends JButton implements MouseListener, 
MouseMotionListener, ActionListener{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	public int num;                                 //  for button name and
	//  text
	private int width;                              //  to store width of button
	private int height;                             //  to store height of 
	//  button
	private Point initialPoint = new Point();       //  to store initial 
	//  position of mouse on 
	//  screen
	private Point buttonPoint = new Point();        //  to store initial button 
	//  position in frame
	private Point currentMousePoint = new Point();  //  to store current
	//  position of mouse on
	//  screen
	private Point finalButtonPoint = new Point();   //  final position of 
	//  button in frame
	public PuzzleLogic puzzle;                      //  declare PuzzleLogic
	//  reference variable
	public int[] origPos = new int[2];              //  new array for tracking
	//  the orinal position
	//  of the button when it is
	//  moved
	public int[] newPos = new int[2];               //  new array for tracking 
	//  the new position of the 
	//  button when it is moved
	public int[] beginPos = new int[2];             //  new array for tracking 
	//  the beginning position 
	//  of the button
	private int deltaX;                             //  the integer offset of 
	//  the button along the 
	//  x-axis when it is to 
	//  have its location reset
	private int deltaY;                             //  the integer offset of 
	//  the button along the 
	//  y-axis when it is to 
	//  have its location reset
	private int delay = 5;                          //  set delay for timer
	//  events
	public Timer timer = new Timer(delay, this);    //  make and initialize a 
	//  new timer object

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition of constructor method MovableButton
	//      Constructor method MovableButton initializes a new MovableButton 
	//  	object with parameters int n and PuzzleLogic puzzle
	////////////////////////////////////////////////////////////////////////////
	public MovableButton(int n, PuzzleLogic puzzle)
	{
		this.puzzle = puzzle;                   //  the puzzle logic to the
		//  PuzzleLogic object that
		//  that was passed
		num = n;                                //  set num to the integer 
		//  passed to it
		setText("" + num);                      //  set the text to num
		setName("" + num);                      //  set the name to num
		addMouseMotionListener(this);           //  add a MouseMotionListener
		addMouseListener(this);                 //  add a MouseListener
		deltaX = 0;                             //  set deltaX to 0
		deltaY = 0;                             //  set deltaY to 0

		//  if n is greater than zero then
		if(n > 0){
			//  set x position
			newPos[0] = origPos[0] = beginPos[0] = (n - 1) - 4 * ((n - 1)/4);
			//  set y position
			newPos[1] = origPos[1] = beginPos[1] = (n - 1)/4;
		}   //  end if(n > 0)
		//  else n = 0
		else{
			//  set position x and y position of invisible empty button
			newPos[0] = origPos[0] = beginPos[0] = 3;
			newPos[1] = origPos[1] = beginPos[1] = 3;
		}   //  end else corresponding to if(n > 0)
	}   //  end definition of constructor public MovableButton(int n, 
	//  PuzzleLogic puzzle)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition of method Reset()
	//      Method Reset() sets the button to its original settings
	////////////////////////////////////////////////////////////////////////////
	public void Reset(){
		//  set deltaX and deltaY to zero
		deltaX = 0;
		deltaY = 0;
		//  set new position and original position to beginning position
		newPos[0] = origPos[0] = beginPos[0];
		newPos[1] = origPos[1] = beginPos[1];
	}   //  end definition method public void Reset()

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition of overriden method mouseDragged
	//      Method mouseDragged overrides the MouseMotionListener method so that 
	//  when the button is dragged it moves only if there is space for it to 
	//  move.
	////////////////////////////////////////////////////////////////////////////
	public void mouseDragged(MouseEvent e){
		width = getWidth();             //  get the width of the button
		height = getHeight();           //  get the height of the button
		currentMousePoint = e.getLocationOnScreen();    //  set the current 
		//  mouse point to the
		//  mouses location on
		//  the screen
		//  if the button has not moved further along the x or y axis than it 
		//  is allowed
		if(deltaX >= - width && deltaX <= width && deltaY >= - height 
				&& deltaY <= height){
			//  set deltaX and deltaY
			deltaX = currentMousePoint.x - initialPoint.x;  
			deltaY = currentMousePoint.y - initialPoint.y;
		}   //  end if(deltaX >= - width && deltaX <= width && deltaY >=  
		//  - height && deltaY <= height)
		//  if the button can move left
		if(canMoveLeft(buttonPoint)){
			//  if the button is within proper bounds
			if(buttonPoint.x + deltaX < buttonPoint.x && buttonPoint.x + deltaX 
					> buttonPoint.x - width){
				//  update location of button
				setLocation(buttonPoint.x + deltaX ,buttonPoint.y);
			}   //  end if(buttonPoint.x + deltaX < buttonPoint.x 
			//  && buttonPoint.x + deltaX > buttonPoint.x - width)
		}   //  end if(canMoveLeft(buttonPoint))
		//  if button can move right
		if(canMoveRight(buttonPoint)){
			//  if the button is within proper bounds
			if(buttonPoint.x + deltaX > buttonPoint.x && buttonPoint.x + deltaX 
					< buttonPoint.x + width){
				//  update location of button
				setLocation(buttonPoint.x + deltaX ,buttonPoint.y);
			}   //  end if(buttonPoint.x + deltaX > buttonPoint.x 
			//  && buttonPoint.x + deltaX < buttonPoint.x + width)
		}   //  end if(canMoveRight(buttonPoint))
		//  if button can move up
		if(canMoveUp(buttonPoint)){
			//  if the button is within proper bounds
			if(buttonPoint.y + deltaY < buttonPoint.y && buttonPoint.y + deltaY 
					> buttonPoint.y - height){
				//  update location of button
				setLocation(buttonPoint.x, buttonPoint.y + deltaY);
			}   //  end if(buttonPoint.y + deltaY < buttonPoint.y  
			//  && buttonPoint.y + deltaY > buttonPoint.y - height)
		}   //  end if(canMoveUp(buttonPoint))
		//  if button can move down
		if(canMoveDown(buttonPoint)){
			//  if the button is within proper bounds
			if(buttonPoint.y + deltaY > buttonPoint.y && buttonPoint.y + deltaY 
					< buttonPoint.y + height){
				//  update location of button
				setLocation(buttonPoint.x, buttonPoint.y + deltaY);
			}   //  end if(buttonPoint.y + deltaY > buttonPoint.y 
			//  && buttonPoint.y + deltaY < buttonPoint.y + height)
		}   //  end if(canMoveDown(buttonPoint))
	}   //  end definition public void mouseDragged(MouseEvent e)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition overriden method mouseReleased
	//      When the mouse is released on a button method mouseReleased sets 
	//  deltaX, deltaY, and finalButtonPoint parameters for the timer event to
	//  animate the button sliding into its correct final position.
	////////////////////////////////////////////////////////////////////////////
	public void mouseReleased(MouseEvent e){
		width = getWidth();                             //  get the width of the
		//  button
		height = getHeight();                           //  get the height of
		//  the button
		newPos[0] = origPos[0];                         //  initialize new x
		//  position to be
		//  original x position
		newPos[1] = origPos[1];                         //  initialize new y
		//  position to be
		//  original y position
		finalButtonPoint = buttonPoint;                 //  initialize 
		//  finalButtonPoint to
		//  original buttonPoint
		currentMousePoint = e.getLocationOnScreen();    //  get the current
		//  mouse point on 
		//  screen
		//  if the button has not moved further along the x or y axis than it 
		//  is allowed
		if(deltaX >= - width && deltaX <= width && deltaY >= - height && deltaY 
				<= height){
			//  set deltaX and deltaY
			deltaX = currentMousePoint.x - initialPoint.x;
			deltaY = currentMousePoint.y - initialPoint.y;
		}   //  end if(deltaX >= - width && deltaX <= width && deltaY >=  
		//  - height && deltaY <= height)
		//  if the button can move up
		if(canMoveUp(buttonPoint)){ 
			deltaX = 0;                                 //  set deltaX to 0
			//  if the button has been moved more than half way into the 
			//  next free space
			if(deltaY < -(int)(height/2)){
				newPos[1] = puzzle.emptyPos[1];         //  set new y position
				//  of button
				puzzle.Move(origPos, newPos);           //  update puzzle
				//  positions
				origPos[0] = newPos[0];                 //  reset original x 
				//  position
				origPos[1] = newPos[1];                 //  reset original y
				//  position
				//  set finalButtonPoint to y-axis position in frame to new
				//  y-axis position
				finalButtonPoint.y = buttonPoint.y - height;
				deltaY = height + deltaY;               //  set deltaY
			}   //  end if(deltaY < -(int)(height/2))
			//  else if deltaY indicates a move in the wrong direction
			else if(deltaY > 0)
			{
				//  set deltaY to zero
				deltaY = 0;
			}   //  end else if(deltaY > 0)
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end if(canMoveUp(buttonPoint))
		//  if the button can move down
		else if(canMoveDown(buttonPoint)){
			deltaX = 0;                                 //  set deltaX to 0
			//  if the button has been moved more than half way into the 
			//  next free space
			if(deltaY > (int)(height/2)){
				newPos[1] = puzzle.emptyPos[1];         //  set new y position
				//  of button
				puzzle.Move(origPos, newPos);           //  update puzzle
				//  position
				origPos[0] = newPos[0];                 //  reset original x 
				//  position
				origPos[1] = newPos[1];                 //  reset original y
				//  position
				//  set finalButtonPoint to y-axis position in frame to new
				//  y-axis position
				finalButtonPoint.y = buttonPoint.y + height;
				deltaY = deltaY - height;               //  set deltaY
			}   //  end if(deltaY > (int)(height/2))
			//  else if deltaY indicates a move in the wrong direction
			else if(deltaY < 0)
			{
				//  set deltaY to zero
				deltaY = 0;
			}   //  end else if(deltaY < 0)
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(canMoveDown(buttonPoint))
		//  if the button can move left
		else if(canMoveLeft(buttonPoint)){
			deltaY = 0;                                 //  set deltaY to 0
			//  if the button has been moved more than half way into the 
			//  next free space
			if(deltaX < -(int)(width/2)){
				newPos[0] = puzzle.emptyPos[0];         //  set new x position
				//  of button
				puzzle.Move(origPos, newPos);           //  update puzzle
				//  positions
				origPos[0] = newPos[0];                 //  reset original x 
				//  positions
				origPos[1] = newPos[1];                 //  reset original y
				//  positions
				//  set finalButtonPoint to x-axis position in frame to new
				//  x-axis position
				finalButtonPoint.x = buttonPoint.x - width;
				deltaX = width + deltaX;                //  set deltaX
			}   //  end if(deltaX < -(int)(width/2))
			//  else if deltaX indicates a move in the wrong direction
			else if(deltaX > 0)
			{
				//  set deltaX to zero
				deltaX = 0;
			}   //  end else if(deltaX > 0)
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(deltaX > 0)
		//  if the button can move right
		else if(canMoveRight(buttonPoint)){
			deltaY = 0;                                 //  set deltaY to 0
			//  if the button has been moved more than half way into the 
			//  next free space
			if(deltaX > (int)(width/2)){
				newPos[0] = puzzle.emptyPos[0];         //  set new x position
				//  of button
				puzzle.Move(origPos, newPos);           //  update puzzle
				//  positions
				origPos[0] = newPos[0];                 //  reset original x 
				//  positions
				origPos[1] = newPos[1];                 //  reset original y
				//  positions
				//  set finalButtonPoint to x-axis position in frame to new
				//  x-axis position
				finalButtonPoint.x = buttonPoint.x + width;       
				deltaX = deltaX - width;                //  set deltaX
			}   //  end if(deltaX > (int)(width/2))
			//  else if deltaX indicates a move in the wrong direction
			else if(deltaX < 0)
			{
				//  set deltaX to zero
				deltaX = 0;
			}   //  end else if(deltaX < 0)
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(canMoveRight(buttonPoint))
	}   //  end definition public void mouseReleased(MouseEvent e)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition overriden method mousePressed
	//      When the mouse is pressed method mousePressed sets initialpoint
	//  and buttonPoint.
	////////////////////////////////////////////////////////////////////////////
	public void mousePressed(MouseEvent e){
		initialPoint = e.getLocationOnScreen();     //  set initialPoint to the
		//  location of the mouse on
		//  the screen
		buttonPoint = getLocation();                //  set buttonPoint to the
		//  location of the button
		//  in the frame
	}   //  end definition of overriden method mousePressed(MouseEvent e)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition method canMoveRight
	//      Method canMoveRight returns Boolean true if the button can move 
	//  right.
	////////////////////////////////////////////////////////////////////////////
	public Boolean canMoveRight(Point buttonPoint){
		int clearXPos = puzzle.emptyPos[0] * width;     //  clearXPos is the
		//  x-pos of the empty
		//  space
		int clearYPos = puzzle.emptyPos[1] * height;    //  clearYPos is the
		//  y-pos of the empty
		//  space
		//  if the clear space is directly to the right of this button
		if(buttonPoint.x + width == clearXPos && getLocation().y == clearYPos){
			return true;    //  return true
		}   //  end if(buttonPoint.x + width == clearXPos && getLocation().y 
		//  == clearYPos)
		//  otherwise, return false
		return false;
	}   //  end public Boolean canMoveRight(Point buttonPoint)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition method canMoveLeft
	//      Method canMoveLeft returns Boolean true if the button can move 
	//  left.
	////////////////////////////////////////////////////////////////////////////
	public Boolean canMoveLeft(Point buttonPoint){
		int clearXPos = puzzle.emptyPos[0] * width;     //  clearXPos is the
		//  x-pos of the empty
		//  space
		int clearYPos = puzzle.emptyPos[1] * height;    //  clearYPos is the
		//  y-pos of the empty
		//  space
		//  if the clear space is directly to the left of this button
		if(buttonPoint.x - width == clearXPos && buttonPoint.y == clearYPos){
			return true;    //  return true
		}   //  end if(buttonPoint.x - width == clearXPos 
		//  && buttonPoint.y == clearYPos)
		//  otherwise, return false
		return false;
	}   //  end definition of method public Boolean canMoveLeft(Point 
	//  buttonPoint)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition method canMoveUp
	//      Method canMoveUp returns Boolean true if the button can move 
	//  up.
	////////////////////////////////////////////////////////////////////////////
	public Boolean canMoveUp(Point buttonPoint){
		int clearXPos = puzzle.emptyPos[0] * width;     //  clearXPos is the
		//  x-pos of the empty
		//  space
		int clearYPos = puzzle.emptyPos[1] * height;    //  clearYPos is the
		//  y-pos of the empty
		//  space
		//  if the clear space is directly above this button
		if(buttonPoint.x == clearXPos && buttonPoint.y - height == clearYPos){
			return true;    //  return true
		}   //  end if(buttonPoint.x == clearXPos && buttonPoint.y - height 
		//  == clearYPos)
		//  otherwise return false
		return false;
	}   //  end definition of method public Boolean canMoveUp(Point buttonPoint)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition method canMoveDown
	//      Method canMoveDown returns Boolean true if the button can move 
	//  down.
	////////////////////////////////////////////////////////////////////////////
	public Boolean canMoveDown(Point buttonPoint){
		int clearXPos = puzzle.emptyPos[0] * width;     //  clearXPos is the
		//  x-pos of the empty
		//  space
		int clearYPos = puzzle.emptyPos[1] * height;    //  clearYPos is the
		//  y-pos of the empty
		//  space
		//  if the clear space is directly bellow this button
		if(buttonPoint.x == clearXPos && buttonPoint.y + height == clearYPos){
			return true;    //  return true
		}   //  end if(buttonPoint.x == clearXPos && buttonPoint.y + height 
		//  == clearYPos)
		//  otherwise return false
		return false;
	}   //  end definition of method public Boolean canMoveDown(Point 
	//  buttonPoint)

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition of method MoveButton
	//      The method MoveButton moves this button to the empty space next
	//  to it if there is emptyspace to move to.
	////////////////////////////////////////////////////////////////////////////
	public void MoveButton(){
		System.out.println("Entered MovableButton method MoveButton");
		width = getWidth();                             //  get the width of the
		//  button
		height = getHeight();                           //  get the height of
		//  the button
		buttonPoint = getLocation();                    //  set buttonPoint to
		//  the buttons 
		//  location in the 
		//  frame
		newPos[0] = origPos[0];                         //  set x 
		//  positions
		newPos[1] = origPos[1];                         //  set y
		//  positions
		//  if the button can move up
		if(canMoveUp(buttonPoint)){
			newPos[1] = puzzle.emptyPos[1];             //  set new y position
			//  of button
			puzzle.Move(origPos, newPos);               //  update puzzle
			//  positions
			origPos[1] = newPos[1];                     //  reset original y
			//  position
			//  set finalButtonPoint to y-axis position in frame to new
			//  y-axis position
			finalButtonPoint.y = newPos[1] * height;
			//  set finalButtonPoint to x-axis position in frame to new
			//  x-axis position
			finalButtonPoint.x = newPos[0] * width;
			deltaY = -(finalButtonPoint.y - buttonPoint.y); //  set deltaY
			deltaX = -(finalButtonPoint.x - buttonPoint.x); //  set deltaX
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end if(canMoveUp(buttonPoint))
		else if(canMoveDown(buttonPoint)){
			newPos[1] = puzzle.emptyPos[1];             //  set new y position
			//  of button
			puzzle.Move(origPos, newPos);               //  update puzzle
			//  positions
			origPos[1] = newPos[1];                     //  reset original y
			//  position
			//  set finalButtonPoint to y-axis position in frame to new
			//  y-axis position
			finalButtonPoint.y = newPos[1] * height;
			//  set finalButtonPoint to x-axis position in frame to new
			//  x-axis position
			finalButtonPoint.x = newPos[0] * width;
			deltaY = -(finalButtonPoint.y - buttonPoint.y); //  set deltaY
			deltaX = -(finalButtonPoint.x - buttonPoint.x); //  set deltaX
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(canMoveDown(buttonPoint))
		else if(canMoveLeft(buttonPoint)){
			newPos[0] = puzzle.emptyPos[0];             //  set new x position
			//  of button
			puzzle.Move(origPos, newPos);               //  update puzzle
			//  positions
			origPos[0] = newPos[0];                     //  reset original x 
			//  positions
			//  set finalButtonPoint to y-axis position in frame to new
			//  y-axis position
			finalButtonPoint.y = newPos[1] * height;
			//  set finalButtonPoint to x-axis position in frame to new
			//  x-axis position
			finalButtonPoint.x = newPos[0] * width;
			deltaX = -(finalButtonPoint.x - buttonPoint.x); //  set deltaX
			deltaY = -(finalButtonPoint.y - buttonPoint.y); //  set deltaY
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(canMoveLeft(buttonPoint))
		else if(canMoveRight(buttonPoint)){
			newPos[0] = puzzle.emptyPos[0];             //  set new x position
			//  of button
			puzzle.Move(origPos, newPos);               //  update puzzle
			//  positions
			origPos[0] = newPos[0];                     //  reset original x 
			//  positions
			//  set finalButtonPoint to y-axis position in frame to new
			//  y-axis position
			finalButtonPoint.y = newPos[1] * height;
			//  set finalButtonPoint to x-axis position in frame to new
			//  x-axis position
			finalButtonPoint.x = newPos[0] * width;
			deltaX = -(finalButtonPoint.x - buttonPoint.x); //  set deltaX
			deltaY = -(finalButtonPoint.y - buttonPoint.y); //  set deltaY
			//  start timer to begin animation of button sliding into position
			timer.start();
		}   //  end else if(canMoveRight(buttonPoint))
	}   //  end definition of method public void MoveButton()

	////////////////////////////////////////////////////////////////////////////
	//  Begin definition of method actionPerformed(ActionEvent e)
	//      Method actionPerformed animates the sliding of button into place.
	////////////////////////////////////////////////////////////////////////////
	public void actionPerformed(ActionEvent e){
		this.removeMouseMotionListener(this);           //  remove 
		//  mouseMotionListener
		//  so user cannot
		//  disrupt animation
		//  and mess up puzzle
		//  logic
		this.removeMouseListener(this);                 //  remove 
		//  mouseListener
		//  so user cannot
		//  disrupt animation
		//  and mess up puzzle
		//  logic
		//  if the button is to the right of the final position
		if( deltaX > 0){
			//  decrease the distance to the final position
			deltaX--;
		}   //  end if( deltaX > 0)
		//  if the button is to the left of the final position
		if( deltaX < 0){
			//  decrease the distance to the final position
			deltaX++;
		}   //  end if( deltaX < 0)
		//  if the button is below the final position 
		if(deltaY > 0){
			//  decrease the distance to the final position
			deltaY--;
		}   //  end if(deltaY > 0)
		//  if the button is above the final position
		if(deltaY < 0){
			//  decrease the distance to the final position
			deltaY++;
		}   //  end if(deltaY < 0)
		//  if the button is in the final position
		if (deltaX == 0 && deltaY ==0) 
		{
			timer.stop();//we stop timer when animation is to end
			//  add the MouseMotionlistener and MouseListener
			this.addMouseMotionListener(this);
			this.addMouseListener(this);
			//  check if the puzzle is solved
			puzzle.Solved();
		}   //  end if (deltaX == 0 && deltaY ==0)
		//  set the location of the button
		setLocation(finalButtonPoint.x + deltaX, finalButtonPoint.y + deltaY);
	}   //  end definition of method public void actionPerformed(ActionEvent e)

	////////////////////////////////////////////////////////////////////////////
	//  The presence of the following methods are present only because class 
	//  MovableButton implements MouseListener,MouseMotionListener, and 
	//  ActionListener and the needn't be overriden for our purposes.
	////////////////////////////////////////////////////////////////////////////
	public void mouseEntered(MouseEvent e){}

	public void mouseClicked(MouseEvent e){}

	public void mouseMoved(MouseEvent e){}

	public void mouseExited(MouseEvent e){}
}   //  end definition class MovableButton extends JButton implements  
//  MouseListener, MouseMotionListener, ActionListener

////////////////////////////////////////////////////////////////////////////////
//END DEFINITION CLASS MOVABLEBUTTON
////////////////////////////////////////////////////////////////////////////////